home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
FishMarket 1.0
/
FishMarket v1.0.iso
/
fishies
/
451-475
/
disk_463
/
ilbm
/
ilbmlib.doc
< prev
next >
Wrap
Text File
|
1992-05-06
|
68KB
|
1,352 lines
Set your editor's TAB width to 3
THE IFF ILBM LIBRARY MANUAL
by Jeff Glatt
6 Sycamore Drive East
New Hartford, NY 13413
*****************************************************************************
1). Introduction
2). Overview
3). High Level Routines
4). Middle Level
5). Handling PROPS
6). Custom FORM Handler
7). Custom (ILBM) Chunk Handler
8). Low Level
9). Adapting old applications
10). ANIM Support
11). Image Size/Scaling
12). ILBMLib Error Msgs
13). Misc Routines
14). Additional Comments
15). Acknowledgements
16). Scary Legal Stuff
*****************************************************************************
1). Introduction
The primary purpose of the ilbm.library is to provide functions to read and
write any kind of IFF file. In particular, there is extra, high level support
for ILBM picture files which simplifies the viewing or saving of a picture.
Furthermore, there are routines useful for creating an ANIM reader/writer/
player.
The ilbm.library is a disk-based runtime library. This means that any
application, upon being run, can open and call routines in this library just
like a program might utilize routines in the Intuition or Graphics libraries.
The ilbm library is easily accessed from C, assembly, and BASIC programs, and
there are examples available in each language.
The library is based upon the original Electronic Arts IFF code (i.e. Open-
RIFF, GetBODY, etc.) as well as a few Commodore enhancements (i.e. Scheppner's
getBitMap, handleCAMG, etc.). There have been a few changes to the internal
logic of some routines, but generally, they perform the same functions as the
original code. A few more significant changes were made to certain routines in
order to accomodate ANIM and non-ILBM files, and also to streamline the parsing
of LISTs, CATs, and PROPs.
The biggest change between the original code and this library is that the
code has been rewritten in the tightest possible 68000 assembly. The size of
the library is < 7000 bytes. A program that uses this library can be smaller
and faster than if the program used the original EA code.¹ Because the library
uses no global data and is therefore re-entrant, many applications can use the
library simultaneously.
Finally, access to the low level IFF functions is provided. The routines,
OpenRIFF, GetChunkHdr, IFFReadBytes, IFFWriteBytes, etc. are all included.
These routines are equivilent to the original EA code except that the lib code
is smaller and faster. Because of this, it is possible to use this library for
any kind of IFF reader and writer with a resulting improvement in the applica-
tion's size and speed.
The current version is 0.5.
*****************************************************************************
2) Overview
The library has functions that fall into three levels: high, middle, and
low. The high level is designed to read or write ILBM files with tremendous-
ly little effort on the part of the application. The middle level requires
more setup, but allows you to construct a reader/writer for any kind of IFF
file. This level handles extremely ugly details like LISTs, CATs, error
checking for IFF "weirdness", and DOS I/O while allowing you control over what
is to be done with individual chunks. The low level maintains DOS I/O and
error checking, but leaves you to deal with LISTs and CATs. Anything that you
used to do with the original EA code, you should be able to do in an almost
identical manner, and adapting your present IFF code to use the library is
quite easy.
Most library routines return an IFFP code. This is a number from 0 to -11,
or in some cases, a 4 byte IFF ID. 0 (IFF_OKAY) usually means success. The
negative numbers mean errors. See the INCLUDE files for all possible numbers.
*****************************************************************************
3). High Level Routines
At the highest levels, the type of IFF file that this library is designed
to read and write is an ILBM.
There are 2 high level functions, LoadIFFToWindow() and SaveWindowToIFF().
The former loads an ILBM into a window, the latter saves a window as an ILBM.
These functions are so comprehensive that they can even be used with inter-
preted languages like BASIC or Rexx.
For saving a file, SaveWindowToIFF() is passed the filename that you
wish to create, and the pointer to the window (whose screen's bitplanes are)
to be saved in IFF ILBM form. The library writes out a complete ILBM with
the proper BMHD, CMAP, CAMG, and BODY chunks. All error checking is done by
the lib. SaveWindowToIFF() returns an IFFP number. 0 means that the save
was successful. Any other number is an error. In the case of error, any
partial file is deleted.
When reading a file, LoadIFFToWindow() takes the filename that you wish
to load, and a special ILBMFrame structure. See the INCLUDE files for a
description of this structure. The C include file is "ILBM_lib.h". The assembly
include is "IFF.i". Basic users should read "BasicUsers".
You MUST allocate this structure and initialize certain fields before
using LoadIFFToWindow().
LoadIFFToWindow() looks for ILBMs inside of CATs and LISTs, sidestepping all
of the other non-ILBM FORMs. It rummages around inside of such a file looking
for the first ILBM that it can find. If it finds an ILBM, it does one of two
things:
1). Loads the imagery into a window that you opened, scaling the picture
to fit the dimensions of the window if that is necessary. What this
means is that you can load a HIRES image into an opened LORES window,
and vice versa, etc.
2). Opens a screen and borderless backdrop window that best "fits" the
picture (e.g. the proper width, height, depth, and viewmodes.
You determine which method is imployed by how you set up the ILBMFrame
before calling LoadIFFToWindow(). The ILBMFrame has iWindow and iScreen
fields. If you zero both these fields, then the library will open a screen
and window into which it loads the picture. The pointers for this new screen
and window will be stored in their respective ILBMFrame fields. The window
will have a default IDCMP of MOUSEBUTTONS. If instead, you open your own
window and screen, you should place these addresses into the ILBMFrame fields
before calling LoadIFFToWindow(). The lib will then load the image into that
window.
Furthermore, you should set up the iUserFlags field. When certain bits of
this field are set, the library will do such things as make the mouse pointer
invisible, or not change your window's present colormap, etc. See the INCLUDE
files for details. Usually, you'll just zero iUserFlags.
You do not have to initialize any of the other ILBMFrame fields.
LoadIFFToWindow() returns an IFFP number. 0 means that the load was
successful. Any other number is an error.
Note that if the library opens the window/screen for you, the pointers are
returned in the ILBMFrame. From this point on, these are your responsibility.
You may ModifyIDCMP() the window, or anything else you desire. You must close
the window and screen when you are finished with them. Even if LoadIFFToWin-
dow() returns an error, you must still get the iWindow and iScreen fields, and
close any non-zero pointer. See the example programs for details.
If neither high level routine serves your purpose, then you will need to
use some mid or low level routines instead.
============================ SaveWindowToIFF ===========================
IFFP = SaveWindowToIFF(fileName, window)
d0 d1 a0
Saves a window's image as an IFF ILBM file. Returns an IFFP code (0 if OK,
a negative, non-zero number for an error. Z-flag set accordingly.)
This procedure calls SaveILBM(), passing in an <x, y> location of <0, 0>,
and a NULL mask. It also assumes you want to write out all the bitplanes
in the BitMap. Also, it applies byte run compression to the BODY.
If an error in saving, it deletes the partial file. The fileName should be
a complete path as might be typed at the CLI (i.e. df0:extras/myName). This
is an ascii, NULL-terminated string. window is the address of the opened
window (as returned from Intuition's OpenWindow).
============================= LoadIFFToWindow ===========================
IFFP = LoadIFFToWindow( fileName, ILBMFrame )
d0 d1 a1
Loads an ILBM file into the ILBMFrame's iWindow. If iWindow is NULL, opens
a screen/backdrop window for the image. This routine calls LoadILBM().
******************************************************************************
4). Middle Level
The middle level routines isolate you from dealing with low level struc-
tures, LISTs, and CATs, but give you the option of installing custom routines
to handle FORMs, PROPs, or chunks inside of ILBM FORMs.
There are two mid level routines for reading/writing ILBMs, LoadILBM()
and SaveILBM(). A third routine, LoadIFF(), is used for non-ILBM forms
such as SMUS. There is a routine that makes saving ANIMs easy, SaveANIM().
Furthermore, LoadILBM() can easily be setup to parse an ANIM file.
We will discuss LoadILBM() and SaveILBM() first.
========================== SaveILBM() ==============================
IFFP=SaveILBM(ViewModes,Compress,fileHandle,Mask,colors,BitMap,xyPoint,handler)
d0 d0 d1 d2 d3 a0 a1 a2 a3
ViewModes - The viewmodes of the screen as an ULONG
Compress - Compression type. 0=None 1=cmpByteRun1
fileHandle - DOS handle of the opened file
Mask - pointer to a mask plane, or NULL if none
colors - pointer to the colorTable to save as a CMAP chunk
BitMap - pointer to the Bitmap structure whose planes are to be saved
as the ILBM BODY
xyPoint - pointer to a structure holding two UWORD values that describe
the xy position to be saved in the BMHD chunk
handler - pointer to a routine to be called before the BODY is written
out, or NULL if none
SaveILBM() writes an entire BitMap as a FORM ILBM in an IFF file. Unlike
SaveWindowToIFF, this routine allows you to save an uncompressed image, or with
a different xy position than 0,0 (for saving a portion of the whole image),
or saving additional chunks in addition to BMHD, CAMG, CMAP, and BODY. It
works for any display mode.
You must open the IFF file to be written before calling SaveILBM(), and
close the file upon return.
SaveILBM() writes out a BMHD whose values are determined by the passed
BitMap's size/depth and the xyPoint. A CAMG chunk is written for the passed
ViewModes. A CMAP chunk is written for the passed colTable.
Then, if you have supplied a passed handler, it is called. This allows an
application to save additional chunks such as CRNG, etc. The lib passes your
handler a GroupContext structure. To write a chunk, you must open the Context
(via OpenRGroup()) and use PutCk(), PutCkKnown(), or calls to IFFWriteBytes
(after PutCkHdr, and ending with PutCkEnd). Your handler should return
IFF_OKAY after it has successfully written out the desired chunks. Any other
IFFP code will terminate SaveILBM() with that error.
Finally, SaveILBM() writes the BODY based on your passed compress mode
and your mask plane.
SaveILBM returns IFF_OKAY if successful. If an error, you must delete the
partial file yourself.
The utility program IFFCheck would print the following outline of the
resulting file:
FORM ILBM
BMHD
CAMG
CMAP
...other chunks that your handler saves
BODY (compressed) or (uncompressed)
=========================== LoadILBM() ===========================
IFFP = LoadILBM(fileHandle, vectors, iframe)
d0 d1 a0 a1
fileHandle - DOS handle of an opened file
vectors - a pointer to a Vectors structure
iframe - a pointer to the master, initialized ILBMFrame structure
Can read an ILBM file. Unlike LoadIFFToWindow, you can arrange for your own
custom routines to handle FORMs, PROPs, and certain ILBM chunks. LoadILBM()
will return IFF_DONE if an image has been successfully loaded into a window by
the lib's default 'FORM' handler. All other IFFP codes indicate that an image
wasn't loaded.
In the case of custom handlers, normal return is IFF_OKAY when the whole
file has been scanned. On the other hand, your custom handler can cause
LoadILBM() to terminate by returning any IFFP error (i.e. IFF_DONE, END_MARK,
IFF_BAD, etc). In this case, LoadILBM() returns that IFFP error.
ILBMFrame's iWindow, iScreen, and iUserFlags must be initialized.
EXTREMELY modified version of ReadPict.c
by Jerry Morrison, Steve Shaw, and Steve Hayes, Electronic Arts.
Modified by C. Scheppner and Jeff Glatt
The library has default routines that it calls whenever it encounters a
LIST, CAT, PROP, FORM, or a chunk inside of a PROP or FORM. LISTs, CATs, and
ILBM PROPs are always handled by the library. On the other hand, you can
replace the default handlers for non-ILBM PROPs, all FORMs, or all ILBM chunks
with your own custom routines. For example, the library can call your FORM
handler each time that it encounters a FORM inside of an IFF file.
Let me explain some details about the default routines. First, the default
FORM handler looks for ILBMs. It can be setup to parse ANIMs and other non-
ILBM forms, but it always looks for ILBMs. For this reason, you should always
supply an ILBMFrame when using the default FORM handler. The handler can find
ILBMs inside of CATs and LISTs, sidestepping all of the other non-ILBM FORMs.
It rummages around inside of such a file looking for the first ILBM that it can
find. It handles the following chunks inside an ILBM:
CMAP BMHD CRNG CCRT CAMG BODY
The CMAP, BMHD, CAMG, CCRT, and CRNG chunks are loaded into the respective
fields of the ILBMFrame (see INCLUDE file). CCRT are converted to CRNG. All
other ILBM chunks are ignored. When it finds the BODY of an ILBM, it does one
of two things:
1). Loads the imagery into a window that you opened, scaling the picture
to fit the dimensions of the window if that is necessary.
2). Opens a screen and borderless backdrop window that best "fits" the
picture (e.g. the proper width, height, depth, and viewmodes).
This should sound familiar. It is exactly the same as LoadIFFToWindow. Just
like that high level function, you should set up the ILBMFrame according to
what you want the lib to do. After the image is loaded, the parsing is stopped
and LoadIFF() returns IFF_DONE if successful. All other error codes indicate
failure.
The default PROP handler handles the following chunks inside an ILBM PROP:
CMAP BMHD CRNG CCRT CAMG
These chunks are loaded into the respective fields of the ILBMFrame with CCRT
converted to CRNG. All other ILBM chunks are ignored. Also, any non-ILBM
PROPs are ignored.
One of the parameters to LoadILBM() is a Vector structure. This is simply
a structure that holds the pointers to whatever routines you would like the
lib to execute while parsing an IFF file. These routines replace the lib's
default handlers. If the Vector pointer is 0, then the default lib routine is
used. You must initialize this structure before calling LoadILBM()!
One field holds a pointer to a routine to handle non-ILBM 'PROP's or an
ILBM PROP chunk that is "unknown" to the lib. The lib doesn't know about the
following ILBM chunks: ANHD, DEST, GRAB, SPRT, and DLTA. It will skip these
in an ILBM PROP if you don't install a PROPhandler. Also, it will skip nonILBM
PROPs if you don't have a PROPhandler. If you don't care, set this field to
NULL.
Another field is for your FORMhandler routine address. If not NULL, this
routine is called instead of the lib's default 'FORM' handler. You are expected
to handle all parsing of FORMs as the library finds them. This means that you
also have to parse the chunks in the FORM. If your application does not deal
with ILBMs at all, you will have to install a custom FORM routine (and use
LoadIFF).
Another field in the Vectors structure is for a CHUNKhandler. This is called
by lib's default 'FORM' handler for all chunks inside an ILBM. If NULL, the
lib's CHUNKhandler will skip the following ILBM chunks:
ANHD DEST GRAB SPRT DLTA
If you don't use the lib's default 'FORM' handler, then this field is ignored.
If you supply a CHUNKhandler, you are telling the library, "If you come across
an ID that you don't understand, don't skip it. Instead, give it to me and I'll
take care of that ILBM chunk".
The last field is for the NonILBMhandler. This is called by the lib's
default 'FORM' handler when it encounters a FORM other than an ILBM (i.e. 8SVX,
etc). If NULL, nonILBM FORMs are skipped over in search of ILBMs. If you
don't use the lib's default 'FORM' handler, then this field is ignored. If you
supply a NonILBMhandler, you are telling the library, "If you come across a
FORM other than ILBM, don't skip it. Instead, give it to me and I'll take care
of that FORM". Note that the library still looks for an ILBM to load into a
window when you use the default 'FORM' routine.
So, by installing routines via the Vectors structure, you can "take away"
certain parsing duties from the lib, while it handles LISTS, CATS, and low
level details. For example, you could install just a CHUNKhandler and set
the other fields to 0. The lib would parse all LISTS, PROPS, CATS, and FORMS.
It would only call your routine for each chunk in an ILBM. See the examples,
ANIMInfo.asm and IFFinfo.c, for details of installing custom routines, and
using mid level routines.
If your application does not deal with ILBMs, you will need to replace the
default FORMhandler and PROPhandler. If you are dealing with ANIMs, you will
only need a custom CHUNKhandler to handle DLTA and ANHD chunks, plus a custom
PROPhandler for those chunks. If you want to load other forms in addition to
ILBMs, then you will need to add a NonILBMhandler.
Here are the parameters passed to your custom vectors. Return an IFFP code.
IFFP = PROPhandler(chunkID,PropID,Context,Vectors,Frame,PROPList)
d0 d0 d2 a0 a2 a3 a4
IFFP = FORMhandler(chunkID,Context,Vectors,Frame,PROPList)
d0 d0 a0 a2 a3 a4
NonILBMhandler and CHUNKhandler same args as FORMhandler.
LoadILBM() zeros out the ILBMFrame's iFlags (but not iUserFlags), iBMAP,
iNumColors, and iCycleCnt fields. You must initialize the iUserFlags field
prior to calling this routine. Various bits of this field affect certain
options (see Include File for details). Other bits are set by the lib to
inform you of certain facts. (i.e. the ANIMB bit would be set by the default
'FORM' handler if an ANIM FORM was encountered in the file.) You also initial-
ize the iWindow and iScreen fields as per LoadIFFToWindow().
====================== LoadIFF() =====================
IFFP = LoadIFF(file, vector, dataAddress)
d0 d1 a0 a1
This is just like LoadILBM() except that it is for reading/writing non-ILBM
forms. If you're using this routine, you should definitely set up the Vector's
FORMhandler to point to a custom routine. Your custom routine should then
parse FORMs using low level functions such as GetFChunkHdr, IFFReadBytes, etc.
The dataAddress parameter is optional. It could be a pointer to a Frame of some
sort (or it could be anything). The lib will pass this to your FORMhandler
as the Frame parameter. It's up to your FORMhandler to decide what it really is
and what to do with it.
Normal return is IFF_OKAY if whole file scanned. On the other hand, your
FORMhandler can cause LoadIFF to terminate by returning any IFFP error (i.e.
IFF_DONE, END_MARK, IFF_BAD, etc). In this case, LoadIFF() returns that IFFP
error.
====================== SaveANIM() ======================
IFFP = SaveANIM(ViewModes,Compress,fileHandle,Mask,colors,BitMap,xyPoint,
d0 d0 d1 d2 d3 a0 a1 a2
FrameHandler,ANHDaddress)
a3 a4
Writes an ANIM file. Writes the passed BitMap as the first frame (FORM
ILBM). Assumes user wants to write out all planes of the BitMap. Calls
application FrameHandler() for writing subsequent frames. Normal return
result is IFF_OKAY. Works for any display mode.
The utility program IFFCheck would print the following outline of the
resulting file:
LIST
PROP ILBM
BMHD
CAMG
CMAP
ANHD ;if passed in
FORM ANIM
FORM ILBM
BODY (compressed) or (uncompressed)
....application routine's saved chunks (i.e. additional FORM ILBMs)
This is similiar to SaveILBM except your FrameHandler is called repeatedly
(so you can write numerous frames) while return = IFF_OKAY. Returning
a 1 stops the Handler loop successfully. Returning an IFFP error aborts
the save. Note that the file written is a LIST with a PROP ILBM. This is
so that each frame need not contain identical data. If the passed ANHD
address is not NULL, the ANHD chunk will be written in the PROP. In this
way, a simple ANIM can be written where the first frame need only contain
a BODY chunk, and subsequent frames contain a DLTA. If all successful,
final return is IFF_OKAY. If an error, you must delete the partial file.
If no FrameHandler, pass a 0.
******************************************************************************
5). Handling PROPs
LoadILBM() (or LoadIFF()) creates a special PROPList to take care of any
PROPS in the file. A PROPList is a linked list structure used to manage Frames
allocated to hold PROP data. There are 4 routines for allocating, examining,
and freeing frame structures from a PROPList.
In order to link a Frame structure (i.e. an ILBMFrame) into this list, we
need to "extend" the Frame with a few extra fields at the beginning of the
structure. We prepend the following fields to the frame, and refer to the
whole thing as a PropFrame.
ULONG *NextPropFrame; /* Pointer to the next frame in PROPList */
LONG ifID;
USHORT PropFrameSize;
So an ILBMPropFrame would be as follows:
typedef struct {
ULONG *NextPropFrame;
LONG ifID;
USHORT PropFrameSize;
struct ILBMFrame PropFrame; /* This is the data part of the PropFrame */
} ILBMPropFrame;
So an ILBMPropFrame has three extra fields prepended to it. It is 10 bytes
larger than an ILBMFrame. The actual, imbedded Frame (i.e. after the first
3 fields) I'll refer to as the data part of the PropFrame.
In assembly, the prepended fields are:
Next dc.l [address of next frame in list]
ID dc.b [the 4 byte type ID] ;type of PropFrame
Size dc.w [size] ;the size of the entire structure (with the
;subsequent data part)
And an ILBMPropFrame would be as follows:
Next dc.l 0
ID dc.b 'ILBM'
size dc.w SizeOfILBMFrame+10
;an ILBMFrame structure immediately follows. This is the data part.
If an ILBM PROP (inside of a LIST) is encountered, the library always
allocates an ILBMPropFrame on your behalf, linking it into the PROP list.
Note that the library ALWAYS takes care of parsing the following chunks in
an ILBM PROP:
BMHD CMAP CAMG CRNG CCRT
It parses the chunk data into the appropriate fields of the ILBMPropFrame. For
other ILBM chunks (i.e. ANHD, DEST, etc), it will call your PROPhandler, pass-
ing the chunkID and a pointer to the newly allocated ILBMPropFrame. The
PropID will be ID_ILBM. You are expected to handle loading/parsing the chunk
data, returning IFF_OKAY if successful. You should either store the chunk's
data somewhere, or use it to modify certain fields of the passed ILBMPropFrame.
Returning any other negative IFFP error terminates LoadILBM() (or LoadIFF)
which also returns that error.
For non-ILBM PROPs, your custom PROPhandler is also called. The PropID is
the type ID of the PROP (i.e. SMUS, 8SVX, SAMP). The chunkID is meaningless.
You should either return IFF_OKAY to ignore the PROP, or parse the PROP using
the low level functions GetPChunkHdr and IFFReadBytes. A convenient approach
would be to allocate a PropFrame for the IFF type. (It's up to your program to
define other types of PropFrames. What's an SMUSPropFrame look like? I don't
know.) Then link it into the PROPList. Use GetPROPStruct() to do all this.
Then parse the PROP chunk data into this allocated PropFrame. Later, your
custom FORMhandler or NonILBMhandler can retreive the desired PropFrame via
SearchPROP(). Your PROPhandler should return an IFFP code. Anything but
IFF_OKAY aborts the load with that error.
If no custom PROPhandler, non-ILBM PROPs and ILBM chunks ANHD, DEST, GRAB,
SPRT, and DLTA are ignored.
======================= GetPROPStruct ======================
Frame = GetPROPStruct(size,typeID,PROPList)
d0 d0 d1 a1
Allocates a PropFrame of passed size, and stores its ID, links it at the
tail of the passed PROPList, and increments PROPList's entries. Returns a
pointer to the PropFrame's data part (imbedded Frame), or 0 if error.
Note that a pointer to the data part is returned (i.e. skips those first
3 "extra" fields) so that you can treat the returned pointer just like a
regular frame. If you were allocating an ILBMPropFrame, the returned pointer
would be that of an ILBMFrame.
The size must include the 3 extra fields.
======================== SearchPROP() =====================
Frame = SearchPROP(ID,PROPList)
d0 d0 a1
Searches the passed PROPList for the last PropFrame with the same ID as
passed. If it finds one of the same type, returns the address of the data part
(the imbedded frame), or 0 if none of that type found in the list.
Used by your custom FORMhandler to search the PROPList for any PropFrames
with the same ID.
======================== CopyILBMProp() ======================
CopyILBMProp(FromFrame,ToFrame)
d0 a1
Copies FromFrame to ToFrame. Both frames must be ILBM frames. It transfers
the iUserFlags field of FromFrame to ToFrame without altering any set bits in
ToFrame's iUserFlags.
If SearchPROP() returns an Frame, you'll want to copy that Frame to the
Frame you intend to use to parse the FORM. This is a routine for copying
ILBMFrames. You'll need to write routines to copy other Frames that you devise.
======================= FreePROPList() ======================
FreePROPList(PROPList)
a1
Frees all the PropFrames in the passed PROPList. Normally, LoadILBM() and
LoadIFF() do this. When using LoadILBM() or LoadIFF(), the lib allocates and
initializes the PROPList. Those routines also free up that PROPList and all its
PropFrames upon termination of the load. This function is provided in case you
are maintaining a second PROPList that you create and need to free.
*****************************************************************************
6). Custom FORM handler
You'll need a custom FORM handler only if you aren't interested in ILBMs.
In this case, you'll also use LoadIFF().
When the lib encounters a FORM, your custom FORM handler is passed a pointer
to an initialized Context structure (to be discussed later). Although no
further initialization of this structure is required on your part, you may need
it for calling certain low level lib functions. You are also passed your master
ILBMFrame so that you can save data to it from within your FORMhandler. You are
passed the type ID of the FORM (i.e. is it an 'ILBM'?). Also, you are passed
a pointer to the PROP list. One of the first things you'll probably want to do
is search it for any PropFrame with the same type ID as the FORM. The function
SearchPROP does this. You may then copy that Frame's data into the Frame that
is used to hold data within your FORMhandler. CopyILBMProp() can copy an
ILBMPropFrame to your master ILBMFrame. Then, as you parse chunks in your
FORMhandler, the new data will overwrite the corresponding PROP data (as it
should). You also are passed the Vectors structure address in case you need it
further. Your FORMhandler is expected to parse the FORM (using a few low level
routines) and eventually return an IFFP code. Anything but IFF_OKAY aborts the
load.
You should use GetFChunkHdr() to read the header of each chunk inside a
FORM. You pass this the GroupContext structure (which the lib passed to your
custom FORM handler). It returns an IFFP code. This should be the chunk ID
if all went well. Otherwise, it will be a negative IFFP number. (Remember,
legal IFF IDs are positive numbers).
ID = GetFChunkHdr(context)
d0 a0
The exception to this is ANIM files. You should use GetF1ChunkHdr for
ANIM files since ANIMs contain imbedded FORMs (ILBMs). Note that this will
cause the lib to reenter your custom FORM routine for each "frame" of the
ANIM. See IFFinfo.c for an example of a custom FORM routine handling ANIMs
and ILBMs. The return is the same as GetF1ChunkHdr.
ID = GetF1ChunkHdr(context)
d0 a0
ID = GetPChunkHdr(context)
d0 a0
******************************************************************************
7). Custom (ILBM) CHUNK Handler
You'll use a custom CHUNKhandler only when you also use the default FORM-
handler. By using a custom CHUNK handler, you determine what is done with
each chunk in an ILBM. You are passed the chunkID so that you can determine
what you are dealing with (i.e. a CMAP, a DLTA, etc.). If you don't want the
chunk, simply return IFF_OKAY. Otherwise, you are responsible for parsing
the rest of the chunk. The size is gotten via the passed GroupContext and the
ChunkMoreBytes macro (see INCLUDE files). NOTE: Do not use DOS Write() to
read in bytes. Always use IFFReadBytes().
If you would prefer the library to handle the chunk, return the chunkID.
The library can handle CMAP, CRNG, CCRT, BMHD, CAMG, and BODY chunks. For the
first five chunks, the data is parsed into the ILBMFrame. If a BMHD or a CAMG,
the respective iFlags bit is set so that you'll know that this property was
found. For CMAP, the iNumColors reflects how many color regs were loaded into
iColorTable. For the BODY chunk, the lib decompresses the image into the
ILBMFrames iWindow (or opens a window if NULL), sets the screen's colors to the
iColorTable values, and terminates LoadILBM() with IFF_DONE.
You might not want the lib to decompress the BODY. Perhaps you are writing
an ANIM reader where you don't want the lib to actually load the first frame
into some window's bitmap's planes. You may prefer to decompress BODY and DLTA
chunks during playback. You can prevent the lib from doing this by handling
ID_BODY yourself (in your CHUNKhandler) rather than returning this ID.
If you handle a chunk yourself, your CHUNKhandler should return IFF_OKAY
to continue parsing the file, or any other negative IFFP number (including
IFF_DONE) to stop the load. It is recommended that you use IFF_DONE to
successfully abort a load since this is what the default FORM handler
does when it reaches an ILBM BODY. This will insure that LoadILBM() always
returns IFF_DONE for aborting the load successfully, END_MARK if you parsed the
entire file without aborting, and other IFFP codes for error conditions.
NOTE: The ANIMFLAG bit of iUserFlags will be set if you are parsing an ANIM
FORM. Remember that the lib calls your CHUNKhandler for ANHD and DLTA,
so for each DLTA you encounter, that is the end of another ANIM Frame
(imbedded ILBM FORM). If ANIMFLAG is not set, then you are in a plain
ILBM file.
******************************************************************************
8). Low Level
If the high level functions LoadIFFToWindow() and SaveWindowToIFF(), or
the mid-level functions SaveILBM(), LoadILBM(), and LoadIFF() aren't suitable,
you may use the low level routines to create a reader/writer.
You will have to keep track of LISTs, CATs, and PROPs as there are no
Vectors Structure, PropFrames, and custom handlers at this level.
The low level routines are the same routines as the original EA code, IFFr.c
and IFFw.c, except that they are smaller and faster asm modules.
At this point, I need to warn you that the arguments passed to some routines
have been changed, as well as the order of the arguments. This was done to make
the C interface code smaller and faster in assembly by using the movem in-
struction. Mostly, the changes are trivial. In fact, the documentation for
these functions is almost the original text.
Unless otherwise stated, the normal (successful) return codes for the low
level routines is IFF_OKAY. The Z and N Flags are set appropriately for low
level functions. This means that assembly programmers can always bne to an
error routine.
These routines ASSUME that they're the only ones reading/writing to the
file. You should only use these routines to read, write, or skip chunks in
an IFF file. Do not use DOS Read, Write, or Seek.
The library's low level routines utilize a 36 byte structure called a
"GroupContext". The GroupContext structure that the library uses is identical
to the original EA structure. Check the INCLUDE files for a description.
The Frame field is now called UserData, but you can use it to pass a pointer
to a Frame from level to sublevel.
For the high and mid-level routines, the lib allocates a new Context (and
initializes it by OpenRIFF or OpenRGroup) for every group (FORM, CAT, LIST,
or PROP) encountered. This is done for you even if you have custom vectors
for FORMs and PROPs. For low level, you need to do this yourself. Any
source code written with the original EA code may be used as an example.
You will only need to deal with Context structures if you are using the
low level routines. This structure is for reading and writing groups and their
chunks. It's just a linked node type of structure for reading (nested) chunks.
A Context structure must be created whenever another level is encountered.
The parentContext field contains the address of another Context structure if
this is not the top level. For example, if a LIST is encountered, a Context
structure is created. For the first FORM in the LIST, another context structure
is created. The parentContext field of the FORM's Context would contain the
address of the LIST's Context structure. The parentContext field of the LIST's
Context structure would be NULL (as long as it wasn't inside of still amother
group). You must read or skip over all the chunks in the FORM before you try to
examine what comes next in the LIST. You can read a chunk via GetChunkHdr() and
IFFReadBytes. You can skip a chunk by calling GetChunkHdr() a second time for
the next chunk's ID.
The UserData field is set to the parent's Userdata field by certain
routines. In this way, every Context's UserData is the same as its parentCon-
text's UserData.
If you use the low level routines properly, the lib will take care of
padding out chunks to even bytes, and keeping track of the total bytes read
in or written out.
The bound field of the Context merits special mention in that it appears to
have no useful purpose. If you place any value other than UNKNOWN there, it
serves as the MAX number of bytes that can be written. So for example, if the
bound = 10000, then you will be prevented from writing out a total of more than
10000 bytes. For most IFF applications, it isn't initially known how many bytes
will eventually be written. All of the IFF writers which I've seen always set
this to UNKNOWN. Also, it doesn't seem useful to have the code impose a
barrier. AmigaDOS already returns an error if you attempt to write beyond a
disc's capacity. I can only assume that this "feature" was included so that
this code would work on other computers with stranger DOS than the Amiga. This
field is used in PutCkEnd to determine whether the lib needs to adjust UNKNOWN
chunkSizes to the real value after it writes out a chunk. If EA had used a bit
flag for UNKNOWN rather than a value to be compared against the bound field,
then the code could be smaller and faster. Then again, the original code was
written in C, a language invented by and for people who can't be bothered with
trivial details like setting bits.
========= Low Level Reader Routines ========
For reading a chunk, the procedure is to allocate a Context structure,
initialize it with OpenRIFF or OpenRGroup, read the chunks with GetChunkHdr
(and its kin) and IFFReadBytes, and close the Context with CloseRGroup or
EndRGroup.
IFFP = OpenRIFF(fileHandle, Context)
d0 d1 a0
Given an open file, this initializes a Context spanning the whole file.
ASSUMES context was allocated by caller but not initialized.
ASSUMES caller doesn't deallocate the context before calling CloseRGroup.
Returns NOT_IFF IFFP code if the file is too small for even a chunk header.
FileSize = FileLength(fileHandle)
d0 d1
Returns the length of the whole file or else a negative IFFP error code of
NO_FILE (fileHandle=0) or DOS_ERROR. Does not change your current position
in the file.
IFFP = OpenRGroup(parent, new) passed 2 Context structures
d0 a0 a1
Open the remainder of the current chunk as a group read context.
This will be called just after the group's subtype ID has been read
(automatically by GetChunkHdr for LIST, FORM, PROP, and CAT) so the
remainder is a sequence of chunks.
This sets new's UserData = parent's UserData.
ASSUMES new context allocated by caller but not initialized.
ASSUMES caller doesn't deallocate the context or access the parent context
before calling CloseRGroup on the new context.
BAD_IFF error if context end is odd or extends past parent.
IFFP = CloseRGroup(context)
d0 a0
Close a group read context, updating its parent context.
After calling this, the old context may be deallocated and the parent
context can be accessed again. It's okay to call this particular procedure
after an error has occurred reading the group.
ID = GetChunkHdr(context)
d0 a0
Skip any remaining bytes of the previous chunk and any padding, then
read the next chunk header into context's chunkID and chunkSize fields.
If the chunkID is LIST, FORM, CAT, or PROP, this automatically reads the
subtype ID into context's subID field.
Caller should dispatch on groupID (and typeID) to an appropriate handler.
RETURNS the chunkID (the 4 ascii bytes of the new chunk header) if it found
another chunk. Otherwise, it will return one of the following errors:
1). END_MARK if there are no more chunks in this context
2). NOT_IFF if at the top level and it isn't a FORM, LIST, or CAT
3). BAD_IFF if a malformed chunk, the chunkSize is negative or too big for
containing context, ID isn't positive, or we hit end-of-file.
Note that if an error, bit #31 of d0 will be set and so you can either
1). move.l d0,d1
bmi to an error routine.
2). btst.l #31,d0
bne to an error routine.
See also GetFChunkHdr and GetPChunkHdr, below.
ID = GetFChunkHdr(context)
d0 a0
Used by a FORM handler. It simply calls GetChunkHdr and returns BAD_IFF if
it encounters an imbedded PROP.
ID = GetPChunkHdr(context)
d0 a0
Used by a PROP handler. It simply calls GetChunkHdr and returns BAD_IFF if
it encounters an imbedded PROP, LIST, CAT, or FORM.
IFFP = IFFReadBytes(nBytes, context, buffer)
d0 d0 a0 a1
Read the specified number of data bytes of current chunk. (Use OpenGroup,
etc. instead to read the contents of a group chunk.) You can call this
several times to read the data piecemeal.
CLIENT_ERROR if nBytes < 0. SHORT_CHUNK if nBytes > remaining bytes
which could be due to a application bug or a chunk that's shorter than it
ought to be (bad form). (If CLIENT_ERROR or SHORT_CHUNK, IFFReadBytes won't
read any bytes.)
IFFP = SkipFwd(bytes, context)
d0 d1 a0
Skip over bytes in a chunk. Won't go backwards.
Updates context's position but not context's bytesSoFar.
NOTE: The original EA code's SkipGroup() routine has been eliminated
since the library is set up to automatically parse LISTS and PROPS.
IFFP = GetCMAP(Context, colorMap, pNColorRegs)
d0 d0 a0 a1
Reads in a CMAP chunk and creates the colorMap at passed address.
pNColorRegs is passed in as a pointer to the number of ColorRegisters
caller has space to hold. GetCMAP sets to the number actually read.
IFFP = GetBODY( bitmap, mask, context, BMHD )
d0 d0 d1 a0 a1
Reads the BODY into the passed bitmap's planes, decompressing if necessary.
Passed the addresses of a BitMap, mask plane (or NULL), the context
structure, and the loaded BitMap header chunk.
BOOL = UnPackRow(dstBytes0, srcBytes0, Source, Dest)
d0 d1 d3 a2 a3
Converts data from "cmpByteRun1" run compression.
control bytes:
[0..127] : followed by n+1 bytes of data.
[-1..-127] : followed by byte to be repeated (-n)+1 times.
-128 : NOOP.
Unpacks one row, returning the source and destination addresses when it
produces dstBytes bytes. This routine no longer uses POINTERS to POINTERS
as in the original Elec Arts code. Also, args in a different order.
=========== Low Level Writing (Save) Routines ==========
These routines will random access back to set a chunk size value when the
caller doesn't know it ahead of time (UNKNOWN size).
The overall scheme is to open an output Group Context via OpenWIFF or
OpenWGroup, call either PutCk or {PutCkHdr {IFFWriteBytes}* PutCkEnd} for
each chunk, then use CloseWGroup to close the Group Context.
To write a group (LIST, FORM, PROP, or CAT), call StartWGroup, write out
its chunks, then call EndWGroup. StartWGroup automatically writes the
group header and opens a nested context for writing the contents.
EndWGroup closes the nested context and completes the group chunk.
IFFP = OpenWIFF(limit, fileHandle, new) new is a pointer to a GroupContext
d0 d0 d1 a0 structure
Given a file open for output, initialize a new, write context.
The "limit" arg imposes a fence or upper limit on the logical file
position for writing data in this context. Pass in UNKNOWN to be limited
only by disk capacity.
ASSUMES new context structure allocated by caller but not initialized.
ASSUMES caller doesn't deallocate the context before calling CloseWGroup.
The caller is only allowed to write out one FORM, LIST, or CAT in this top
level context (see StartWGroup and PutCkHdr).
CLIENT_ERROR if limit is odd.
IFFP = StartWGroup(groupType, groupSize, subtype, parent, new)
d0 d0 d1 d2 a0 a1
parent and new are the addresses of Context structures, groupType and
subtype are IDs, groupsize is a LONG
Start writing a group (presumably LIST, FORM, PROP, or CAT), opening a
nested context. The groupSize includes all nested chunks + the subtype ID.
The subtype of a LIST or CAT is a hint at the contents' FORM type(s). Pass
in FILLER (" ") if it's a mixture of different kinds.
This writes the chunk header via PutCkHdr, writes the subtype ID via
IFFWriteBytes, and calls OpenWGroup. The caller may then write the nested
chunks and finish by calling EndWGroup.
The OpenWGroup call sets new's ILBMFrame to parent's ILBMFrame.
ASSUME new context structure allocated by caller but not initialized.
ASSUME caller doesn't deallocate the context or access the parent context
before calling CloseWGroup.
ERROR conditions: See PutCkHdr, IFFWriteBytes, OpenWGroup.
IFFP = OpenWGroup(parent, new) parent and new are GroupContext structures
d0 a0 a1
Open the remainder of the current chunk as a group write context.
This is normally only called by StartWGroup.
Any fixed limit to this group chunk or a containing context will impose
a limit on the new context.
This will be called just after the group's subtype ID has been written
so the remaining contents will be a sequence of chunks.
This sets new's UserData = parent's UserData.
ASSUME new context structure allocated by caller but not initialized.
ASSUME caller doesn't deallocate the context or access the parent context
before calling CloseWGroup.
CLIENT_ERROR if context end is odd or PutCkHdr wasn't called first.
IFFP = EndWGroup(old)
d0 a0
End a group started by StartWGroup.
This just calls CloseWGroup and PutCkEnd.
ERROR conditions: See CloseWGroup and PutCkEnd.
IFFP = CloseWGroup(old) old is the address of a Context structure
d0 a0
Close a write context and update its parent context.
This is normally only called by EndWGroup.
If this is a top level context (created by OpenWIFF) we'll set the file's
EOF (end of file) but won't close the file.
After calling this, the old context may be deallocated and its parent
context can be accessed again.
Amiga DOS Note: There's no call to set the EOF. We just position to the
desired end and return. Caller must Close file at that position.
CLIENT_ERROR if PutCkEnd wasn't called first.
IFFP = PutCk(chunkID, chunkSize, context, data)
d0 d0 d1 a0 a1
Writes a whole chunk to a Context. This writes the chunk ID, chunkSize,
data bytes, and (if needed) a pad byte. It also updates the Context to
reflect how many bytes have been written to the file. Returns CLIENT_ERROR
if chunkSize = UNKNOWN. This is because you must know how many bytes of
data you wish to write in order to use this routine. (i.e. Use this routine
instead of a PutCkHdr/IFFWriteBytes/PutCkEnd series of calls when you know
exactly how many bytes will be in the chunk). See also PutCkHdr errors.
IFFP = PutCkHdr(chunkID, chunkSize, context)
d0 d0 d1 a0
Writes just an 8 byte chunk header. The chunk header consists of the 4 byte
ascii ID, and the chunkSize LONG. You should follow this will any number of
calls to IFFWriteBytes in order to write out the chunk data. Finally, when
all the chunk data is output, call PutCkEnd.
If you don't yet know how big the chunk is, pass in chunkSize = UNKNOWN,
then PutCkEnd will set the chunkSize for you later. This method is used
when you really don't know how many data bytes will eventually get written
out. (i.e. maybe you're compressing the data as it's being written out and
you don't want to bother with knowing or keeping track of how many bytes
have been written out. The lib does this for you as long as you specify
chunkSize = UNKNOWN and you use only the IFFWriteBytes routine to write data
to the file).
Otherwise, IFFWriteBytes and PutCkEnd will ensure that the specified
number of bytes get written.
CLIENT_ERROR if the chunk would overflow the Context's bound, if
PutCkHdr was previously called without a matching PutCkEnd, if chunkSize
< 0 (except UNKNOWN), if you're trying to write something other
than one FORM, LIST, or CAT in a top level (file level) context, or
if chunkID <= 0 (these illegal ID values are used for error codes).
IFFP = IFFWriteBytes(nBytes, context, data)
d0 d0 a0 a1
Write nBytes number of data bytes for the current chunk and update the
Context.
Returns CLIENT_ERROR if any of the following conditions:
1). Writing nBytes of data would overflow the Context's limit or
current chunk's chunkSize. If you have specified these fields to be
UNKNOWN, then this condition is not applicable.
2). If PutCkHdr wasn't called first.
3). nBytes < 0 (i.e. ridiculously large).
IFFP = PutCkEnd(context)
d0 a0
Complete the current chunk, write a pad byte if needed, and update the
Context.
If current chunk's chunkSize = UNKNOWN, this goes back and sets the
chunkSize in the file.
CLIENT_ERROR if PutCkHdr wasn't called first, or if the application hasn't
written 'chunkSize' number of bytes with IFFWriteBytes.
IFFP = InitBMHdr(masking, compression, transparentColor,
d0 d0 d1 d2
pageWidth, pageHeight, bmHdr0, bitmap)
d3 d4 a0 a1
Initializes a BitMap Header (BMHD) chunk to the passed values, and sets the
BMHD's aspect ratio.
IFFP PutCMAP(depth, context, colorMap)
d0 d0 a0 a1
Writes out the passed colorMap (actually colorTable) as a CMAP chunk.
IFFP = PutBODY(mask, context, bmHdr, bitmap)
d0 d0 d1 a0 a1
Writes the BODY chunk to disk in compressed or uncompressed form.
bytes, newSource, newDest = PackRow(rowSize, pSource, pDest)
d0 a0 a1 d0 a0 a1
Given addresses of source and dest, packs one row, returning the new source
and destination addresses in a0 and a1. RETURNs count of packed bytes in d0.
Please note that this routine needs the actual addresses to the source and
destination buffers unlike the original EA code which wanted PTRS to PTRS.
That technique is unnecessary for assembly applications which can access
multiple return values in several registers. I decided to put the ineffic-
iency where it really belongs; in the C application. For C programmers, the
new source and dest addresses can be found at sourceptr and destptr respec-
tively. These are globals contained in the module ILBMInterface.asm which
must be assembled and linked with your application. These globals can be
accessed after a call to PackRow so that you'll have the addresses to pass
on the next, subsequent call. Of course, you'll be able to access the
returned number of packed bytes as a normal return. Ultimately what this
means is that if you call PackRow from a C application, you can never make
that application fully re-entrant (unless you put a FORBID/PERMIT around
the call).
**************************************************************************
9). Adapting old IFF applications
If you already have an old application that uses the Electronic Arts code,
there are several approaches that you could take to adapt the program to use
the ilbm lib instead.
The easiest approach is to ask yourself, "Can I use LoadIFFToWindow() or
SaveWindowToIFF()?" If all that you're doing with IFF is saving or loading
ILBM pictures in standard Intuition windows, the answer is yes. Get rid of ALL
the EA code and INCLUDE files. Dump ReadPict.c, ReadIFF.c, IFFr.c, etc. Follow
the example ShowPic.c (or ShowPic.asm). Then check the size of your program.
If you're doing ANIMs, non-ILBM (SMUS, 8SVX, etc.) or overscan screen views,
you'll need to persue a second approach. Use all of the low level routines of
the lib in place of the similiarly named EA functions. Section 5 of this
document lists all of the low level functions (i.e. GetCMAP, OpenWGroup, PutCk,
GetBODY, etc.) Remember that these are functionally identical to the original
EA code.
For example, you've probably got OpenRGroup() somewhere in your program.
(Check the EA code that you're using as well). Found OpenRGroup()? Did you
modify it? No? Then you're all set. Rip the function OpenRGroup() out of your
code. Add the line
#include "ilbm_lib.h"
at the top of the source code. You are now using the ilbm lib's OpenRGroup()
instead of the original EA code.
Do this with all of the low level routines you find throughout your program.
Just be careful to note the order of the parameters. They may have changed,
and if so, you will have to modify any routine that calls the replaced func-
tion. For example, a call to the EA routine, PutCkHdr() is as follows:
PutCkHdr(context, ckID, ckSize);
The ilbm lib's PutCkHdr has a different order for those 3 args:
PutCkHdr(ckID, ckSize, context);
Otherwise, the two functions are the same. Several low level routines have a
different arg order. This was done for the sake of efficiency in the lib's
C interface. Also, note that a few low level functions no longer need
certain args. For example, the EA GetBODY() is:
GetBODY( context, bitmap, mask, bmHdr, buffer, bufsize );
The ilbm lib is:
GetBODY( bitmap, mask, context, bmHdr );
The library supplies its own buffer. You don't have to. (That's one more thing
you can get rid of in your source code.)
Finally, you should get rid of any EA INCLUDE files, and all INCLUDE
statements in your program that reference those files. You don't need them.
"ilbm_lib.h" replaces all EA include files (i.e. IFF.h, PutPict.h, ReadPict.h,
Packer.h, and ILBM.h). When you link your program, you will have to assemble
the file "ILBMInterface.asm", and link with that.
Of course, if you can make use of the mid level functions by rewriting your
program to use all of their special features, your program will be that much
smaller and faster. Unfortunately, this is not as straightforward as substitu-
ting the low level functions.
*****************************************************************************
10). ANIM Support
There are a few routines to assist in the construction of an ANIM player.
These routines can unpack BODY and DLTA chunks, modifying a BitMap's planes
in order to construct the next frame of an animation. Also there is a routine
to setup a BitMap structure initially based on a loaded BMHD chunk.
The normal procedure for an anim player would be to either replace the lib's
default 'FORM' handler with a custom handler, or use a CHUNKhandler set up to
handle ID_BODY, ID_ANHD, and ID_DLTA. This handler should load the first
frame's BMHD and BODY, and the ANHD and DLTA for all subsequent frames. Also,
you should set up a PROPhandler to parse any ID_ANHD in an ILBM PROP.
Then you can play back the anim as follows:
1). Allocate a raster large enough to hold the decompressed image. You can
determine the size by the fields in the BMHD.
height = BMHD's h or pageHeight, whichever is larger
planedepth = numPlanes * height
width = BMHD's w or pageWidth, whichever is larger
RowBytes = (15+width)/8, rounded down to the nearest integer
RasterSize = RowBytes * planeDepth
get CHIP mem for the raster
2). Setup the BitMap structure to be used for displaying the ANIM by calling
the ilbm lib's SetupBitMap. Pass your allocated raster to SetupBitMap.
You will need to set up the View, ViewPort, and colorMap yourself.
3). Decompress the BODY into the BitMap's planes (i.e. your allocated
raster) using the library's DecompBODY(). For double-buffered animation,
you will need to setup an identical Bitmap/raster and copy the first
image into the new raster.
4). Display the BitMap's planes (i.e. the first frame).
5). Modify the BitMap's planes for the next frame via DecompDLTA().
6). Repeat from step 4 until no more frames.
Here are the 3 routines that you'll use in an ANIM player.
===================== SetupBitmap() ===================
SetupBitmap(raster,bitmap,BMHD)
d0 a0 a1
Initializes passed bitmap's depth, rows, and BytesPerRow based on passed
BMHD's (BitMapHeader) x, y, and numPlanes, then stores addresses into bitmap's
planes[] of each plane within the passed raster. (All the planes in the raster
form 1 contiguous CHIP mem block). Raster must be large enough for numPlanes
* BMHD's (x+15)/8 * BMHD's y.
After an ANIM is loaded, and you allocate a raster (via AllocRaster maybe)
based on the BMHD's x, y, and numPlanes, you can use this routine to setup
a BitMap structure in order to decompress the BODY with DecompBODY and
start calling DecompDLTA.
================== DecompBODY() ===================
DecompBODY(BODYdata, BMHD, Bitmap)
a0 a1 a2
Decompresses a BODY chunk's data into the BitMap's planes based on the
passed BMHD (BitMapHeader) chunk's w,y,numPlanes. This can be used to make
the first frame of an ANIM.
=================== DecompDLTA() ==================
DecompDLTA(DLTAdata,Bitmap)
a0 a2
Decompresses a DLTA chunk's data into the BitMap's planes (which must still
have the previous frame's image). This routine calls MakeYTable and
DecodeVKPlane, and can be used to "make" the next frame of an animation
being double-buffered.
You probably won't need to use these next 2 functions unless you're doing
something unusual with DLTA data.
===================== MakeYTable() =====================
MakeYTable(width, height, table)
d0 d1 a0
Makes a ytable for use with DecodeVKPlane. Table should be an memblock
capable of holding 500 WORDs (1000 bytes).
=================== DecodeVKPlane() ========================
DecodeVKPlane(linebytes,ytable,in,out)
d0 d1 a0 a1
By Jim Kent. Modified JG. Copyright 1987 Dancing Flame all rights reserved.
Decompresses a DLTA chunk in vertical-byte-run-with-skips compression mode.
where in is a bit-plane's worth of vertical-byte-run-with-skips data
and out is a bit-plane that STILL has the image from last frame on it.
Linebytes is the number of bytes-per-line (USHORT) in the out bitplane, and
it should certainly be noted that the passed variable ytable must be
initialized to point to a multiplication table of 0*linebytes, 1*linebytes
... n*linebytes before this routine is called.
Each entry in ytable is a USHORT.
The format of "in":
Each column of the bitplane is compressed separately. A 320x200
bitplane would have 40 columns of 200 bytes each. The linebytes
parameter is used to count through the columns, it is not in the
"in" data, which is simply a concatenation of columns.
Each columns is an op-count followed by a number of ops.
If the op-count is zero, that's ok, it just means there's no change
in this column from the last frame.
The ops are of three classes, and followed by a varying amount of
data depending on which class.
1. Skip ops - this is a byte with the hi bit clear that says how many
rows to move the "dest" pointer forward, ie to skip. It is non-
zero
2. Uniq ops - this is a byte with the hi bit set. The hi bit is
masked down and the remainder is a count of the number of bytes
of data to copy literally. It's of course followed by the
data to copy.
3. Same ops - this is a 0 byte followed by a count byte, followed
by a byte value to repeat count times.
***************************************************************************
11). Image Size/Scaling
When you ask the lib to load an image into an already opened window, some-
times the image won't be the exact same size as the window. You may be trying
to load a HIRES picture (640 x 200) into a LORES screen (320 x 200), or vice
versa. You may even be trying to load a picture larger than a standard Amiga
screen size (i.e. 660 x 220) into a standard size screen. The library has a
routine that can automatically "scale" the picture to fit your window. LORES
pictures will be "expanded" to fill a HIRES display, and HIRES will be "shrunk"
to fit a LORES. Non-standard size pictures will likewise be scaled to fit a
standard screen. Because of this, you never need worry about what size picture
a user has chosen to display in your window. It will always fit.
The lib's scaling routine makes multiple calls to graphics lib routines
which appear to do temporary mem alloc/dealloc. Unfortunately, the dealloca-
tion is not done in the same order as the allocation. The net result: severe
memory fragmentation. Oddly enough, this problem appears to only inflict LORES
pictures being scaled into a HIRES screen. A HIRES picture into a LORES screen
works fine. It appears likely that you may not be able to load a LORES picture
into a HIRES screen twice in a row. Alas, the Amiga operating system is not
smart enough to re-coalese free blocks when it could. You have to reboot the
computer. You want smart memory coalesing, buy a MAC. Despite this, I decided
that scaling the picture yielded more pleasing results than cropping it. If
memory fragmentation problems persist, or you don't want a "smaller" picture to
be scaled to fill a "larger" display area, set the NOSCALE flag of the ILBM-
Frame's iUserFlags. This will cause LORES pictures to only fill as much of a
larger display as they normally would. Larger pictures are always scaled to
fit smaller displays.
This scaling is meant to fit an image with a given width and height into
a screen with a different width and/or height. The lib does not currently
adjust for different depths (number of planes). For this reason, an image
with a depth of 5 will probably end up with "weird" colors when loaded into
a screen with a depth of 4. Perhaps in the future, a routine will be added
to interpolate color tables if there appears to be interest in such a feature.
Note that the process of scaling a picture to fit a different size screen
is notably slow. Get used to it.
Here is the routine that scales a section of one BitMap to fit into a
section of another RastPort. It is passed a standard, graphics structure
called a Rectangle.
BOOL ScaleImage(dest_rectangle,source_rectangle,dest_rport,source_bitmap)
d0 a0 a1 a2 a4
Passed pointers to the source bitmap, source rectangle structure (current
dimensions), destination rastport (to fit source into), and dest rectangle
struct (desired dimensions). Scales the rectangular chunk (as described by
source_rectangle) of source bitmap into the rectangular chunk (as described by
dest_rectangle) of destination rastport. It achieves this by remapping the
color value of each pixel, discarding or adding extra pixels per the scaling
dimensions using an array for the colors. This does not create a larger or
smaller palette of colors if the # of planes is different. It simply fits one
image of given width and height into a different size raster. Alters the passed
sorce BitMap's planes while transfering. It only does one line at a time of the
source so it is SLOW.
Returns a 1 if success, 0 if error (no mem for color array).
If you set the ILBMFrame's Flags ADJUSTVIEW bit and allow the library to
open the window/screen combo, the screen's view will be adjusted so that the
image is centered in the amiga's display. This means that an overscanned image
(i.e. larger than 320 x 200 if LORES, 640 x 200 if HIRES) will be centered in
the display. This also takes INTERLACE and PAL into account. On the other hand,
an image smaller than a standard Intuition screen will be centered as well.
***************************************************************************
12). ILBMlib Error Msgs
Your calling routines should check all returned IFFP error codes. Don't
press on after an error! The lib routines try to have no side effects if an
error, except that partial I/O is sometimes unavoidable. Any routine could
return DOS_ERROR. In that case, ask DOS for the specific error code, if
desired, via the DOS library's IOErr() routine.
Here is a routine to help display error messages to the user. This routine
returns a pointer to a NULL-terminated string which describes the IFFP error
number. You can then display this string to the user. See the INCLUDE files
for the meaning of certain IFFP codes.
String = GetIFFPMsg(IFFP)
d0 d0
****************************************************************************
13). Misc Routines
Here's a routine to make the mouse pointer in the passed window "disappear".
Use Intuition's ClearPointer() or SetPointer() to change it again later.
BlankPointer(window)
a0
****************************************************************************
14). Additional Comments
An effort was made to test all of the lib functions in a variety of ways,
but I'm sure that there are things which have escaped me. Please send any
bug reports to the above address. I am not offering free consultation to
anyone, but if you have a particular problem or question, I will try to
assist. If a particular piece of code appears not to work with the library,
I would be interested in seeing the code. There will almost certainly be new,
improved lib versions in the future. These will be sent to Fred Fish.
An article in AmigaWorld's premiere Tech Journal has an assembly example of
using this library in an ANIM player, in which I demo using some ANIM oriented
routines of the library.
The modules that you should have are as follows:
ILBMLib.Doc this text file
ilbm.library the library (to be copied to your boot disk's libs drawer)
IFF.i assembly language include file
ILBM_Lib.h C Include file
ShowPic.c a C example of using the lib's high level functions
ILBMInterface.asm the awful "poot" that is required for any C example
BasicILBM an AmigaBasic example
ilbm.bmap the bmap file for the Basic example
ilbm_lib.fd the Basic fd file
BasicUsers additional info for Basic programmers
ShowPic.asm an assembly example using the high level functions
ANIMInfo.asm an assembly example of installing a custom FORM handler
and using mid/low level functions
ANIMInfo an executable of the above
ShowPic a re-entrant ILBM viewer with color-cycling (operates
like CBM's "Display" program)
IFFinfo.c a C example of installing a custom FORM handler and using
mid/low level functions
AboutIFF a straightforward explanation of IFF file format
Play8SVX.c a C example for using mid/low level routines to load an
8SVX file
*****************************************************************************
15). Acknowledgements
The ilbm.library is based upon much code placed in the public domain by
several individuals. The programmers who offer such favors are the ones
who are really responsible for the Amiga still being a viable product.
Once again, I want to mention the people who helped make this public
domain offering possible:
Jerry Morrison, Steve Shaw, and Steve Hayes, Electronic Arts.
C. Scheppner, CATS.
Jim Kent, Dancing Flame.
This is one of those few efforts in which I didn't steal "something" from
Bryce Nesbitt. Bryce is such a good programmer and documentator that it is
a fluke that he actually works for CBM.
*****************************************************************************
16). Scary Legal Stuff
"OK. IF I USE IT, WHAT'S IT GONNA COST ME."
The ilbm.library may be used by and FREELY distributed with any application
be it commercial or public domain.
There are no pagan users fees, Trump-esque licenses, or other forms of rabid
capitalist trickery associated with using this library and its support files.
You do not even have to acknowledge the secret of your expedient and memory
efficient IFF I/O routines.
The only limitation is that you may not alter the actual executable of the
ilbm.library, nor sell the library and its support files as a distinct product
(i.e. represent it as such).
============================= FOOTNOTES ===============================
¹ The routines that read and write data to disc (IFFReadBytes and IFFWrite-
bytes) use unbuffered I/O. An application that uses the buffered version
of EA's code will probably be faster but certainly not as memory effic-
ient. Wouldn't you rather wait an extra 15 seconds to load an ANIM file
rather than not be able to view it at all because the loader is sucking
up RAM? Besides, if you want to waste memory with buffers that remain idle
except during disk I/O, why not use the CLI ADDBUFFERS command. This way,
you can speed up the lib to comparable levels with the buffered IFF code,
and also allow other disc I/O code to benefit. I realize that the concept
of conserving RAM is not popular amoung non-assembly programmers, most
of whom write applications which unnecessarily require 1 MEG to run well.
² In order to handle PROPs more efficiently and with less stack use than
the original EA code, I made the following limitation.
There should be no LIST imbedded within a LIST.
In a file that violates this rule, PROP data from the outer LIST may
affect chunks inside the inner LIST. Since I have never seen imbedded
LISTs, I decided that such abominations don't deserve special treatment.